using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.Xml.Schema;


namespace ISYBAUSchemaExporter
{    
    public class OracleSchemaContext
    {
        private OracleSchemaNode mNode;
        private bool mIsOptional;

        public OracleSchemaContext(OracleSchemaNode node, bool optional)
        {
            mNode = node;
            mIsOptional = optional;
        }

        public OracleSchemaNode Node
        {
            get { return mNode; }
            set { mNode = value; }
        }

        public bool Optional
        {
            get { return mIsOptional; }
            set { mIsOptional = value; }
        }
    }
    
    public class OracleSchemaVisitor : IVisitor
    {
        private List<string> mGeometryTypeList;

        public readonly static string FIELD_AUTO_ID = "AUTO_ID";

        public readonly static string GEOM_TABLE_NAME = "GEOM_POINT";

        public readonly static string GEOM_FIELD_NAME = "DATA";

        private Dictionary<string, OracleSchemaTable> mOracleTableList;
        
        private Queue<string> mTypeList;

        public OracleSchemaVisitor()
        {
            mOracleTableList = new Dictionary<string, OracleSchemaTable>();
            mTypeList = new Queue<string>();
            mGeometryTypeList = new List<string>();
            InitialGeometryTypeList();
        }


        private void InitialGeometryTypeList()
        {
            //mGeometryTypeList.Add("FlPunktType");
            mGeometryTypeList.Add("PunktType");
            mGeometryTypeList.Add("FlPointType");
            mGeometryTypeList.Add("PointType");
        }

        public Dictionary<string, OracleSchemaTable> TableDictionary
        {
            get { return mOracleTableList; }
        }

        public void Visit(XmlSchema node, Object context)
        {
            foreach (XmlSchemaElement element in node.Elements.Values)
            {
                Visit(element, null);
            }
        }

        public void Visit(XmlSchemaElement node, Object context)
        {
            if (node.ElementSchemaType is XmlSchemaSimpleType)
            {
                ReadSimpleType(node, context);
            }
            else if (node.ElementSchemaType is XmlSchemaComplexType)
            {
                ReadComplexType(node, context);
            }
        }

        public void Visit(XmlSchemaParticle node, Object context)
        {
            if (node is XmlSchemaElement)
            {
                Visit(node as XmlSchemaElement, context);
            }
            else if (node is XmlSchemaGroupRef)
            {
                Visit(node as XmlSchemaGroupRef, context);
            }
            else if (node is XmlSchemaChoice)
            {
                Visit(node as XmlSchemaChoice, context);
            }
            else if (node is XmlSchemaSequence)
            {
                Visit(node as XmlSchemaSequence, context);
            }
        }

        public void Visit(XmlSchemaGroupRef node, Object context)
        {

        }

        public void Visit(XmlSchemaChoice node, Object context)
        {
            if (context != null)
            {
                OracleSchemaContext parentContext = context as OracleSchemaContext;
                OracleSchemaTable parentTable = parentContext.Node as OracleSchemaTable;
                
                bool nullable = (node.MinOccurs > 0 ? false:true);
                int index = parentTable.Fields.Count;
                OracleSchemaChoiceField choiceField = new OracleSchemaChoiceField("CHOICE", null, nullable, index, node);
                parentTable.AddField(choiceField);
                foreach (XmlSchemaObject child in node.Items)
                {
                    Visit(child, null);
                }
            }            
        }

        public void Visit(XmlSchemaSequence node, Object context)
        {
            foreach (XmlSchemaObject child in node.Items)
            {
                Visit(child, context);
            }
        }

        private void Visit(XmlSchemaObject node, Object context)
        {
            if (node is XmlSchemaElement)
            {                
                Visit(node as XmlSchemaElement, context); 
            }
            else if (node is XmlSchemaGroupRef)
            {
                Visit(node as XmlSchemaGroupRef, context);
            }
            else if (node is XmlSchemaChoice)
            {
                Visit(node as XmlSchemaChoice, context);
            }
            else if (node is XmlSchemaSequence)
            {
                Visit(node as XmlSchemaSequence, context);
            }
        }

        private void ReadSimpleType(XmlSchemaElement node, Object context)
        {
            XmlSchemaSimpleType simpleType = node.ElementSchemaType as XmlSchemaSimpleType;
            bool nullable = (node.MinOccurs > 0 ? false : true);
            bool isCollectionField = (node.MaxOccurs > 1 ? true : false);

            string name = node.Name;            
            string type = simpleType.Name;
				XmlSchemaDerivationMethod deriveMethod = simpleType.DerivedBy;
				bool isSpecialDomainType = false;
				if (String.IsNullOrEmpty(type)&& deriveMethod == XmlSchemaDerivationMethod.Restriction && simpleType.BaseXmlSchemaType != null && simpleType.BaseXmlSchemaType.DerivedBy == XmlSchemaDerivationMethod.Restriction && !String.IsNullOrEmpty(simpleType.BaseXmlSchemaType.Name))
				{
					isSpecialDomainType = true;
				}

            OracleSchemaField simpleField;

            OracleSchemaContext parentContext = context as OracleSchemaContext;
            if (parentContext != null)
            {
                OracleSchemaTable parentTable = parentContext.Node as OracleSchemaTable;

                if ((type == null || type == string.Empty) && !isSpecialDomainType)
                {
                    // simple field with directly value
                    simpleField = new OracleSchemaField(name, simpleType, nullable);
                }
					 else if (!isSpecialDomainType)
					 {
						 // this is ISYBAU customized simple type: enum simple type
						 OracleSchemaTable referencedTable;
						 if (!mOracleTableList.ContainsKey(type))
						 {
							 mTypeList.Enqueue(type);
							 referencedTable = new OracleSchemaTable(type, simpleType);
							 mOracleTableList.Add(type, referencedTable);
						 }
						 else
						 {
							 referencedTable = mOracleTableList[type];
						 }
						 if (isCollectionField)
						 {
							 simpleField = new OracleSchemaCollectionField(name, simpleType, nullable, referencedTable, parentTable);
						 }
						 else
						 {
							 simpleField = new OracleSchemaDomainField(name, simpleType, nullable, referencedTable);
						 }
					 }
					 else
					 {
						 // handle special domain type
						 OracleSchemaTable referencedTable;
						 XmlSchemaSimpleType specialType = simpleType.BaseXmlSchemaType as XmlSchemaSimpleType;
						 string specialTypeName = specialType.Name;
						 if (!mOracleTableList.ContainsKey(specialTypeName))
						 {
							 mTypeList.Enqueue(specialTypeName);
							 referencedTable = new OracleSchemaTable(specialTypeName, specialType);
							 mOracleTableList.Add(specialTypeName, referencedTable);
						 }
						 else
						 {
							 referencedTable = mOracleTableList[specialTypeName];
						 }
						 if (isCollectionField)
						 {
							 simpleField = new OracleSchemaCollectionField(name, specialType, nullable, referencedTable, parentTable);
						 }
						 else
						 {
							 simpleField = new OracleSchemaDomainField(name, specialType, nullable, referencedTable);
						 }
					 }
                parentTable.AddField(simpleField);
            }
        }

        private void ReadComplexType(XmlSchemaElement node, object context)
        {
            XmlSchemaComplexType complexNode = node.ElementSchemaType as XmlSchemaComplexType;
            string name = node.Name;
            string type = node.SchemaTypeName.Name;

            if (string.IsNullOrEmpty(type) )
            {
                if (!mOracleTableList.ContainsKey(name))
                {
                    type = name;
                }
                else
                {
                    string parentType = string.Empty;
                    if (context != null)
                    {
                        OracleSchemaContext inputContext = context as OracleSchemaContext;
                        string tableName = (inputContext.Node as OracleSchemaTable).Name;
                        if (tableName.Contains("_"))
                        {
                            string[] parts = tableName.Split('_');
                            parentType = parts[parts.Length - 1];
                        }
                        else
                        {
                            parentType = tableName;
                        }
                        type = string.Format("{0}_{1}", parentType, name);
                    }
                    else
                    {
                        type = name;
                    }
                }
            }      
            
            if (IsGeometryType(complexNode))
            {
                ReadGeomType(node, context);               
            }
            else
            {
                OracleSchemaTable oracleTable = new OracleSchemaTable(type, complexNode);
                // add ID field in the corrsponding table to represent the REF_TYPE_ID in parent table
                oracleTable.AddField(CreateRefIDField());

                OracleSchemaContext schemaContext;

                if (context == null)
                {
                    // start the new type                
                    schemaContext = new OracleSchemaContext(oracleTable, false);

                    if (!mOracleTableList.ContainsKey(type))
                    {                       
                        mOracleTableList.Add(type, oracleTable);
                    }
                    else
                    {
                        mOracleTableList[type] = oracleTable;
                    }                   

                    Visit(complexNode.ContentTypeParticle, schemaContext);
                    oracleTable.Initialized = true;

                    // read the nested complex schema type
                    while (mTypeList.Count > 0)
                    {
                        string typeName = mTypeList.Dequeue();
                        OracleSchemaTable nestedTable = mOracleTableList[typeName] as OracleSchemaTable;
                        if (!nestedTable.Initialized)
                        {
                            
                            OracleSchemaContext parentContext = new OracleSchemaContext(nestedTable, true);
                            ReadSchemaType(typeName, nestedTable.SchemaNode, parentContext);
                            nestedTable.Initialized = true;
                        }
                    }
                }
                else
                {
                    // complex type act as a property
                    schemaContext = context as OracleSchemaContext;
                    bool nullable = (node.MinOccurs > 0 ? false : true);
                    bool isCollectionField = (node.MaxOccurs > 1 ? true : false);

                    if (schemaContext != null)
                    {
                        OracleSchemaTable parentTable = schemaContext.Node as OracleSchemaTable;
                        OracleSchemaReferenceField field;
                        if (!isCollectionField)
                        {
                            field = new OracleSchemaReferenceField(name, complexNode, nullable, oracleTable);
                        }
                        else
                        {
                            field = new OracleSchemaCollectionField(name, complexNode, nullable, oracleTable, parentTable);                            
                        }

                        parentTable.AddField(field);

                        if (!mOracleTableList.ContainsKey(type))
                        {
                            mTypeList.Enqueue(type);
                            mOracleTableList.Add(type, oracleTable);
                        }
                    }
                }
            }
        }

        private void ReadGeomType(XmlSchemaElement node, object context)
        {
            XmlSchemaComplexType complexNode = node.ElementSchemaType as XmlSchemaComplexType;
            string name = node.Name;
            string type = node.SchemaTypeName.Name;

            bool nullable = (node.MinOccurs > 0 ? false : true);
            bool isCollectionField = (node.MaxOccurs > 1 ? true : false);

            OracleSchemaContext schemaContext = context as OracleSchemaContext;
            if (schemaContext != null)
            {
                OracleSchemaTable parentTable = schemaContext.Node as OracleSchemaTable;
                OracleSchemaField geomField; 
                if (!isCollectionField)
                {
                    geomField = new OracleSchemaGeomField(name, complexNode, nullable);
                    parentTable.AddField(geomField);
                }
                else
                {
                    OracleSchemaTable geomTable;
                    string geomTableName = OracleSchemaGeomTable.TABLE_NAME;
                    if (mOracleTableList.ContainsKey(geomTableName))
                    {
                        geomTable = mOracleTableList[geomTableName];
                    }
                    else
                    {
                        geomTable = new OracleSchemaGeomTable(geomTableName, null);
                        geomTable.Initialized = true;
                        mOracleTableList.Add(geomTableName, geomTable);

                    }

                    geomField = new OracleSchemaCollectionField(name, complexNode, nullable, geomTable, parentTable);
                    
                    parentTable.AddField(geomField);
                }
            }
        }

        private void ReadSchemaType(string typeName, XmlSchemaType node, object context)
        {
            string name = node.Name;
    
            if (node is XmlSchemaComplexType)
            {
                Visit((node as XmlSchemaComplexType).ContentTypeParticle, context);
            }
            else
            {
                //parse enumnation type simple type
                OracleSchemaDomainTable domainTable = new OracleSchemaDomainTable(name, node);

                XmlSchemaSimpleType simpleType = node as XmlSchemaSimpleType;
                XmlSchemaSimpleTypeRestriction content = simpleType.Content as XmlSchemaSimpleTypeRestriction;
                if (content != null)
                {
                    int index = 1;
                    foreach (XmlSchemaObject item in content.Facets)
                    {
                        if (item is XmlSchemaEnumerationFacet)
                        {
                            XmlSchemaEnumerationFacet itemfacet = item as XmlSchemaEnumerationFacet;
                            string itemShortValue = itemfacet.Value;
                            string itemAnnotation = GetAnnotation(itemfacet.Annotation);

                            domainTable.AddValue(new OracleSchemaDomainValue(index++, itemAnnotation, itemShortValue));
                        }
                    }
                }
                domainTable.Initialized = true;
                mOracleTableList[typeName] = domainTable;
            }
        }

        #region Utility methods
        private int GetStringLength(XmlSchemaSimpleType node)
        {
            int result = 0;
            XmlSchemaSimpleTypeRestriction content = node.Content as XmlSchemaSimpleTypeRestriction;

            if (content != null)
            {
                foreach (XmlSchemaObject item in content.Facets)
                {
                    if (item is XmlSchemaMaxLengthFacet)
                    {
                        result = Convert.ToInt32((item as XmlSchemaMaxLengthFacet).Value);
                        break;
                    }
                    else if (item is XmlSchemaLengthFacet)
                    {
                        result = Convert.ToInt32((item as XmlSchemaLengthFacet).Value);
                        break;
                    }
                }
            }
            return result;
        }
        
        private string GetAnnotation(XmlSchemaAnnotation annotation)
        {
            string result = string.Empty;
            if (annotation.Items.Count > 0)
            {
                foreach (XmlSchemaDocumentation doc in annotation.Items)
                {
                    result += doc.Markup[0].InnerText;
                }
            }

            return result;
        }

        private string GenerateEnumTable()
        {
            return string.Empty;
        }

        private string GetOracleDataType(XmlTypeCode typeCode)
        {
            return typeCode.ToString();
        }

        public static OracleSchemaField CreateRefIDField()
        {
            OracleSchemaField field = new OracleSchemaField(FIELD_AUTO_ID, null, false);
            field.TypeExpress = IDTypeExpress();
            return field;
        }
        
        public string DumpResult()
        {
            string statement = string.Empty;
            foreach (OracleSchemaTable entry in mOracleTableList.Values)
            {
                if (entry.Initialized)
                {
                    statement += entry.SQLExpress();
                }
            }

            return statement;
        }

        public string DumpDropResult()
        {
            string statement = string.Empty;
            foreach (OracleSchemaTable entry in mOracleTableList.Values)
            {
                if (entry.Initialized)
                {
                    statement += entry.DropSQL();
                }
            }
            return statement;
        }

        public static string IDTypeExpress()
        {
            return "NUMBER(10,0)";
        }

        public static string BoolTypeExpress()
        {
            return "NUMBER(1,0)";
        }

        private bool IsGeometryType(XmlSchemaComplexType xmlNode)
        {
            bool result = false;
            string typeName = xmlNode.Name;
            if (mGeometryTypeList.Contains(typeName))
            {
                result = true;
            }
            return result;
        }
        #endregion Utility methods
    }
}
